#include "StdAfx.h"
#include "AGAnimation.h"
#include "AnimationGraphState.h"
#include "AnimationRandomizer.h"
#include <NameCRCHelper.h>

#define LAYER_NAMES LAYER_NAMES_AGANIM

static const char * LAYER_NAMES[] = {
	"AnimationLayer1",
	"AnimationLayer2",
	"AnimationLayer3",
	"AnimationLayer4",
	"AnimationLayer5",
	"AnimationLayer6",
	"AnimationLayer7",
	"AnimationLayer8",
	"AnimationLayer9",
};


void CAGAnimation::EnterState( SAnimationStateData& data, bool dueToRollback )
{
	ICharacterInstance* pICharacter = data.pEntity->GetCharacter(0);
	if (!pICharacter)
	{
		//GameWarning("Entity '%s' of class '%s' has no character attached", data.pEntity->GetName(), data.pEntity->GetClass()->GetName());
		return;
	}
	IAnimationSet* pAnimSet = pICharacter->GetIAnimationSet();

	CAnimationPlayerProxy *animPlayerProxy = data.animationProxy;
	CRY_ASSERT_MESSAGE(animPlayerProxy != NULL, "No animation player proxy defined in the data");

	if (!animPlayerProxy)
		return;

	if(gEnv->IsDedicated())
		return;

	// If the animation graph state is paused we shouldn't touch the animation queue (no playing anims, no stopping anims)
	const bool bAnimationGraphIsPaused = data.isPaused;

	if ( !gEnv->IsClient() )
	{
		data.hurried = false;
	}
	else if ( !HasAnimation() /*m_animations[0].empty()*/ )
	{
		if ( m_layer > 0 )
		{
			data.hurried = false;
			const CAnimation* anim = data.animationProxy->GetTopAnimation(data.pEntity, m_layer);
			if (anim && (anim->m_AnimParams.m_nFlags & CA_LOOP_ANIMATION) && !bAnimationGraphIsPaused)
			{
				animPlayerProxy->StopAnimationInLayer(data.pEntity, m_layer, 0.3f );
			}
		}
	}
	else
	{
		if (m_animRandomizer)
		{
			m_animRandomizer->EnterState();

			// is this asset randomized and should alternate from time to time?
			// or does it have an initial delay?
			if (m_animRandomizer->GetAnimationSwitchingEnabled() || m_animRandomizer->HasInitialDelay())
			{
				this->flags |= eASNF_Update;
			}
		}

		int animationId = -1;
		string animation0;
		if (m_animRandomizer)
		{
			if (!m_animRandomizer->HasInitialDelay())
			{
				m_animRandomizer->ChooseNextAnimation();
				animation0 = m_animRandomizer->GetCurrentAnimationName().c_str();
				animation0 = data.pState == NULL ? animation0.c_str() : data.pState->ExpandVariationInputs( animation0.c_str() ).c_str();
				animationId = pAnimSet->GetAnimIDByName(animation0);
				if (animationId < 0 && !animation0.empty())
					GameWarning("Entity \"%s\":cannot find animation \"%s\"", data.pEntity->GetName(), animation0.c_str());
			}
		}
		else
		{
			animationId = RetrieveAnimationId(data.pState, pAnimSet);

			/*
			 * Remove this for now, since it is spamming too much - we dont have a complete set of nw animations
			 *
			
			if ((m_flags&hasVariationInputsInName) && (animationId < 0))
				GameWarning("Entity \"%s\":cannot find animation with id = %i", data.pEntity->GetName(), animationId);
			*/
		}

		if ((animationId >= 0) && !bAnimationGraphIsPaused)
			StartNodeAnimation(animationId, data, dueToRollback);
	}
}

CryCharAnimationParams CAGAnimation::GetAnimationParams( SAnimationStateData& data )
{
	CryCharAnimationParams params = data.params[m_layer];
	if ( data.overrideTransitionTime != 0.0f )
		params.m_fTransTime = data.overrideTransitionTime;

	if (data.overrides[m_layer].overrideStartAfter)
		params.m_nFlags |= CA_START_AFTER;
	if (data.overrides[m_layer].overrideStartAtKeyFrame)
	{
		params.m_nFlags |= CA_START_AT_KEYTIME;
		params.m_fKeyTime = data.overrides[m_layer].startAtKeyFrame;
	}

	// clear overrides after consuming
	data.overrides[m_layer].ClearOverrides();

// 	if (m_oneShot)
// 		params.m_nFlags &= ~CA_LOOP_ANIMATION;

	if (m_animRandomizer && !m_animRandomizer->GetAnimationSwitchingEnabled() && (data.params->m_nFlags & CA_LOOP_ANIMATION))
	{
		// state is looping but has a randomizer that is only playing one animation --> use Repeat last Key
		params.m_nFlags |= CA_REPEAT_LAST_KEY;
	}

	params.m_nLayerID = m_layer;
//	params.m_nCopyTimeFromLayer = -1;
	m_bUseLastToken = false;

	ICharacterInstance* pCharacter = data.pEntity->GetCharacter(0);
	ISkeletonAnim* pSkeletonAnim = pCharacter ? pCharacter->GetISkeletonAnim() : NULL;

	if (pSkeletonAnim)
	{
		const int iNumAnims = pSkeletonAnim->GetNumAnimsInFIFO(m_layer);
		if (iNumAnims > 0)
		{
			const CAnimation& anim = pSkeletonAnim->GetAnimFromFIFO(m_layer, iNumAnims - 1);

			// special case: switching between 2 states using the exact same animation, reuse last user token
			if(anim.m_nAnimID == RetrieveAnimationId(data.pState, pCharacter->GetIAnimationSet()))
			{
				m_bUseLastToken = true;
			}
		}
	}
		
	params.m_nUserToken = m_bUseLastToken ? m_lastUserToken : data.pState->GetCurrentToken();

	float playBackSpeed = m_MPSpeedMultiplier;
	if (data.pGameObject->GetChannelId() == 0) /*CCryAction::GetCryAction()->IsMultiplayer()*/
	{
		// single player
		playBackSpeed = m_SPSpeedMultiplier;
		if (m_RandomizeSpeedMulPercent != 0.0f)
		{
			// randomize the playback speed
			// xx% up or down
			playBackSpeed = ((((cry_frand() * 2.0f * m_RandomizeSpeedMulPercent) - m_RandomizeSpeedMulPercent)) / 100.0f + 1.0f) * playBackSpeed;
		}
	}
	params.m_fPlaybackSpeed = playBackSpeed;

	params.m_fUserData[eAGUD_AnimationControlledView] = data.animationControlledView? 1.0f : 0.0f;
	params.m_fUserData[eAGUD_MovementControlMethodH] = data.MovementControlMethodH;
	params.m_fUserData[eAGUD_MovementControlMethodV] = data.MovementControlMethodV;
	params.m_fUserData[eAGUD_AdditionalTurnMultiplier] = data.additionalTurnMultiplier;

	for (size_t i=eAGUD_NUM_BUILTINS; i<NUM_ANIMATION_USER_DATA_SLOTS; i++)
		params.m_fUserData[i] = data.userData[i];

	if (m_flags&interruptCurrentAnim)
		params.m_nFlags &= ~(CA_START_AFTER | CA_START_AT_KEYTIME);

	if ( (m_layer == 0) && (data.canMix == false) )
		params.m_nFlags |= CA_DISABLE_MULTILAYER;

	if ( data.pState->IsUsingTriggeredTransition() || (m_flags&fullRootPriority) )
		params.m_nFlags |= CA_FULL_ROOT_PRIORITY;

	return params;
}

EHasEnteredState CAGAnimation::HasEnteredState( SAnimationStateData& data )
{
	// no animation, no randomizer and no variation inputs?
	if (!HasAnimation()) 
	{
		if (m_layer!=0)
		{
			if (ICharacterInstance * pChar = data.pEntity->GetCharacter(0))
			{
				ISkeletonAnim* pSkel = pChar->GetISkeletonAnim();
				int nAnimsInFIFO = pSkel->GetNumAnimsInFIFO(m_layer);
				return nAnimsInFIFO ? eHES_Waiting : eHES_Entered;
			}
		}
		return eHES_Instant;
	}

	if (!gEnv->IsClient())
		return eHES_Entered;

	const CAnimation* anim = data.animationProxy->GetAnimation(data.pEntity, m_layer, m_bUseLastToken ? m_lastUserToken : data.pState->GetCurrentToken());

	if (anim)
	{
		if (anim->m_bActivated || (data.pEntity->IsHidden() && data.pEntity->GetFlags()&ENTITY_FLAG_UPDATE_HIDDEN) || !(data.pEntity->GetSlotFlags(0)&ENTITY_SLOT_RENDER))
			return eHES_Entered;
		else
			return eHES_Waiting;      
	}

	return eHES_Instant;
}

bool CAGAnimation::CanLeaveState( SAnimationStateData& data )
{
	if (!HasAnimation())
		return true;

	bool found = false;
	bool activated = IsActivated( data, &found );

	if (found && activated && m_stayInStateUntil > 0.001f)
	{
		const CAnimation* anim = data.animationProxy->GetAnimation(data.pEntity, m_layer, m_bUseLastToken ? m_lastUserToken : data.pState->GetCurrentToken());
		if (anim)
		{
			const float nextAnimTime = anim->m_fAnimTime + anim->m_fCurrentDeltaTime;

			if (nextAnimTime < m_forceInStateUntil)
				return false;
			if ((!data.queryChanged) && (nextAnimTime < m_stayInStateUntil))
				return false;
		}
	}

	if (found && (m_flags&ensureInStack) && activated)
	{
		if (!IsTopOfStack(data))
			return false;
	}

	if (found)
		return activated;

	return true;
}

bool CAGAnimation::IsActivated(const SAnimationStateData& data, bool * pFound /* = NULL  */)
{
	bool activated = false;
	bool found = false;

	const CAnimation* anim = data.animationProxy->GetAnimation(data.pEntity, m_layer, m_bUseLastToken ? m_lastUserToken : data.pState->GetCurrentToken());
	if (anim)
	{
		found = true;

		// hacky: allow activation for hidden characters [MR/Craig]
		activated = anim->m_bActivated || (data.pEntity->IsHidden() && data.pEntity->GetFlags()&ENTITY_FLAG_UPDATE_HIDDEN) || !(data.pEntity->GetSlotFlags(0)&ENTITY_SLOT_RENDER);
	}

	if (pFound)
		*pFound = found;
	return activated;
}

void CAGAnimation::LeaveState( SAnimationStateData& data )
{
	if (!HasAnimation())
		return;

	const CAnimation* anim = data.animationProxy->GetAnimation(data.pEntity, m_layer, m_bUseLastToken ? m_lastUserToken : data.pState->GetCurrentToken());
	if (anim)
	{
		if (!anim->m_bActivated)
		{
			data.animationProxy->RemoveAnimationInLayer(data.pEntity, m_layer, m_bUseLastToken ? m_lastUserToken : data.pState->GetCurrentToken());
		}
		else if (anim->m_fAnimTime >= m_forceInStateUntil && anim->m_fAnimTime < m_stayInStateUntil)
			data.overrides[m_layer].ClearOverrides();
	}

	if (m_flags&forceLeaveWhenFinished)
		data.pState->InvalidateQueriedState();

	if (m_animRandomizer)
		m_animRandomizer->LeaveState();
}

void CAGAnimation::DebugDraw( SAnimationStateData& data, IRenderer * pRenderer, int x, int& y, int yIncrement )
{
	float white[] = {1,1,1,1};

	// even if m_animName is empty, this will be ok
	// if strings are enabled on consoles, it won't be empty (unless it's a null state)
	// It will be filled in GetDesc()
	const char* szAnimation0 = m_animName.c_str();

	ICharacterInstance* pCharacter = data.pEntity->GetCharacter(0);
	IAnimationSet* pAnimSet = pCharacter ? pCharacter->GetIAnimationSet() : NULL;
	const int animationId = RetrieveAnimationId(data.pState, pAnimSet);
	if (pAnimSet && (animationId >= 0))
	{
		szAnimation0 = pAnimSet->GetNameByAnimID(animationId);
	}

	pRenderer->Draw2dLabel( (float)x,(float)y, 1.f, white, false, "anim: %s", szAnimation0 );
	y += yIncrement;
	if (m_flags&ensureInStack)
	{
		pRenderer->Draw2dLabel( (float)x,(float)y, 1.f, white, false, "ensure top of stack: %d", IsTopOfStack(data) );
		y += yIncrement;
	}
	if (m_flags&forceLeaveWhenFinished)
	{
		pRenderer->Draw2dLabel( (float)x,(float)y, 1.f, white, false, "force leave state when finished" );
		y += yIncrement;
	}
	if (m_forceInStateUntil < m_stayInStateUntil)
	{
		pRenderer->Draw2dLabel( (float)x,(float)y, 1.f, white, false, "stay in state until: %f (%f forced)", m_stayInStateUntil, m_forceInStateUntil );
		y += yIncrement;
	}
	else if (m_stayInStateUntil)
	{
		pRenderer->Draw2dLabel( (float)x,(float)y, 1.f, white, false, "stay in state until: %f", m_stayInStateUntil );
		y += yIncrement;
	}
	pRenderer->Draw2dLabel( (float)x,(float)y, 1.f, white, false, "is activated: %d", IsActivated(data) );
	y += yIncrement;
}

void CAGAnimation::Paused(SAnimationStateData& data, bool paused, float fOverrideTransTime/* = -1.0f*/)
{
	if (!paused)
	{
		if (ICharacterInstance* pCharacter = data.pEntity->GetCharacter(0))
		{
			if (IAnimationSet* pAnimSet = pCharacter->GetIAnimationSet())
			{
				const int animationId = RetrieveAnimationId(data.pState, pAnimSet);

				if (!IsAnimTopOfStack(animationId, data))
				{
					if (fOverrideTransTime >= 0.0f)
					{
						float fOriginalTransitionTime = data.overrideTransitionTime;
						data.overrideTransitionTime = fOverrideTransTime;
						StartNodeAnimation(animationId, data, false);
						data.overrideTransitionTime = fOriginalTransitionTime;
					}
					else
					{
						StartNodeAnimation(animationId, data, false);
					}
				}

				if (!(m_flags & isLooping))
					data.animationProxy->SetAnimTime(data.pEntity, m_layer, m_bUseLastToken ? m_lastUserToken : data.pState->GetCurrentToken(), m_fAnimTimeWhenPaused);
			}
		}
	}
	else
	{
		const CAnimation* pAnim = data.animationProxy->GetAnimation(data.pEntity, m_layer, m_bUseLastToken ? m_lastUserToken : data.pState->GetCurrentToken());
		if (pAnim)
			m_fAnimTimeWhenPaused = pAnim->m_fAnimTime;
	}
}

void CAGAnimation::Update( SAnimationStateData& data )
{
	if (data.isPaused)
		return;

	float animTime = -1.0f;

	if ((m_flags&forceLeaveWhenFinished) && IsTopOfStack(data))
	{
		const CAnimation* anim = data.animationProxy->GetAnimation(data.pEntity, m_layer, m_bUseLastToken ? m_lastUserToken : data.pState->GetCurrentToken());
		if (anim)
		{
			animTime = anim->m_fAnimTime;
			const float waitUntilTime = 0.99f;
			if (anim->m_fAnimTime >= waitUntilTime)
			{
				data.pState->ForceLeaveCurrentState();
			}
		}
	}

	if (m_animRandomizer)
	{
		// retrieve animTime if it hasn't been done already
		if (animTime == -1.0f)
		{
			const CAnimation* anim = data.animationProxy->GetAnimation(data.pEntity, m_layer, m_bUseLastToken ? m_lastUserToken : data.pState->GetCurrentToken());
			if (anim)
				animTime = anim->m_fAnimTime;
		}

		m_animRandomizer->Update();

		if (m_animRandomizer->IsItTimeForAnimationSwitch(animTime))
		{
			// start next one
			string animName = m_animRandomizer->ChooseNextAnimation().c_str();

			ICharacterInstance * pCharacter = data.pEntity->GetCharacter(0);
			if (pCharacter)
			{
				IAnimationSet* pIAnimationSet = pCharacter->GetIAnimationSet();
				animName = data.pState == NULL ? animName.c_str() : data.pState->ExpandVariationInputs( animName.c_str() ).c_str();
				int animationId = pIAnimationSet->GetAnimIDByName(animName);

				StartNodeAnimation(animationId, data, false);
			}
		}
	}
}

bool CAGAnimation::IsTopOfStack( const SAnimationStateData& data )
{
	ICharacterInstance * pCharacter = data.pEntity->GetCharacter(0);
	if (!pCharacter)
		return true;
	IAnimationSet* pIAnimationSet = pCharacter->GetIAnimationSet();
	ISkeletonAnim* pSkeletonAnim = pCharacter->GetISkeletonAnim();

	int nAnimsCurrently = pSkeletonAnim->GetNumAnimsInFIFO(m_layer);
	switch (nAnimsCurrently)
	{
	case 0:
		// no animation playing, better get one
		return true;
	case 1:
		{
			// this is the last animation in the queue - must be on top of the stack
			return true;
		}
		break;
	default:
		// > 1 animation playing... wait
		return false;
	}
}

void CAGAnimation::GetCompletionTimes( SAnimationStateData& data, CTimeValue start, CTimeValue& hard, CTimeValue& sticky )
{
	hard = 0.0f;
	if (m_stickyOutTime > 0)
		sticky = start + m_animationLength - m_stickyOutTime;
	else
		sticky = 0.0f;
}

const IAnimationStateNodeFactory::Params * CAGAnimation::GetParameters()
{
	static const Params params[] = 
	{
		{true,  "string", "animation1",    "Animation 1",                                      "" },
		{true,  "string", "animation2",    "Animation 2",                                      "" },
		{true,  "string", "aimPose",    "AimPose 1",                                      "" },
		{true,  "string", "aimPose2",    "AimPose 2",                                      "" },
		{true,  "bool",   "ensureInStack", "Wait for animation to be playing?",                "0"},
		{true,  "float",  "stickyOutTime", "Sticky out time (or <0 for non-sticky playback)",  "-1"},
		{true,  "bool",   "forceEnableAiming", "Force enable aiming and usage of aimposes",  "0"},
		{true,  "bool",   "forceLeaveWhenFinished", "Force leaving this state when finished",  "0"},
		{true,  "float",  "speedMultiplier", "Speed multiplier (increases playback speed on this animation)", "1"},
		{true,  "float",  "MPSpeedMultiplier", "Multiplayer speed multiplier (increases playback speed on this animation in multiplayer)", "1"},
		{true,  "bool",   "stopCurrentAnimation", "Stop the current animation?",                "0"},
		{true,  "bool",   "interruptCurrAnim", "Interrupt current animation?",                "0"},
		{true,	"bool",   "noAim", "If true, aim IK has no influence on the character when this state is active", "0"},
		{true,  "bool",   "fullRootPriority", "If true, set CA_FULL_ROOT_PRIORITY animation flag", "0"},
		{0}
	};
	return params;
}

bool CAGAnimation::Init( const XmlNodeRef& node, IAnimationGraphPtr pGraph )
{
	m_animRandomizer = NULL;
	m_flags = 0;

	bool ok = true;
	bool temp = false;
	ok &= node->getAttr("ensureInStack", temp);
	if (temp)
	{
		m_flags |= ensureInStack;
	}
	ok &= node->getAttr("stickyOutTime", m_stickyOutTime);
// 	if (!node->getAttr("forceEnableAiming", m_forceEnableAiming))
// 		m_forceEnableAiming = false;
	temp = false;
	ok &= node->getAttr("forceLeaveWhenFinished", temp);
	if (temp)
	{
		m_flags |= forceLeaveWhenFinished;
	}

	string tempAnimName = node->getAttr("animation1");

	m_animName = pGraph->RegisterVariationInputs( tempAnimName );
	if (m_animName != tempAnimName)  // variation inputs or randomizer
		m_flags |= hasVariationInputsInName;
	else if (tempAnimName.find("RandomSet") != string::npos)
	{
		// Get the Randomizer Setup from the pGraph here, these have been previously loaded
		m_animRandomizer = pGraph->GetRandomizerSetupInstance(tempAnimName.c_str());
		m_flags |= hasVariationInputsInName;
	}

	// Hash Value Calculation so we don't have to store big animation names later
	m_CRC32AnimName = 0;
	if (!(m_flags&hasVariationInputsInName) && !m_animName.empty())  // no randomizer and no variation inputs --> we can convert to hash value!
	{
		char pStringArray[MAX_STATIC_CHARS];
		NameCRCHelper::MakeLowercase(pStringArray, m_animName.c_str());
		m_CRC32AnimName = gEnv->pSystem->GetCrc32Gen()->GetCRC32(pStringArray); 
	}

	m_RandomizeSpeedMulPercent = 0.0f;
	if (node->haveAttr("RandomizeSpeedMulPercent"))
		node->getAttr("RandomizeSpeedMulPercent", m_RandomizeSpeedMulPercent);
	if (m_RandomizeSpeedMulPercent != 0.0f)
	{
		CryLogAlways("XML Loading Speed Randomization of %.2f%%", m_RandomizeSpeedMulPercent);
	}	

	m_SPSpeedMultiplier = 1.0f;
	if (node->getAttr("speedMultiplier")[0])
		node->getAttr("speedMultiplier", m_SPSpeedMultiplier);
	m_MPSpeedMultiplier = m_SPSpeedMultiplier;
	if (node->getAttr("MPSpeedMultiplier")[0])
		node->getAttr("MPSpeedMultiplier", m_MPSpeedMultiplier);
	m_stayInStateUntil = 0;
	node->getAttr("stayInStateUntil", m_stayInStateUntil);
	m_forceInStateUntil = m_stayInStateUntil;
	if (node->getAttr("forceInStateUntil")[0])
	{
		node->getAttr("forceInStateUntil", m_forceInStateUntil);
		if ( m_forceInStateUntil > m_stayInStateUntil )
			m_stayInStateUntil = m_forceInStateUntil;
	}

	temp = false;
	node->getAttr("stopCurrentAnimation", temp);
	if (temp)
	{
		m_flags |= stopCurrentAnimation;
	}

	temp = false;
	node->getAttr("interruptCurrAnim", temp);
	if (temp)
	{
		m_flags |= interruptCurrentAnim;
	}

// 	m_directToLayer0 = false;
// 	node->getAttr("directToLayer0", m_directToLayer0);

	if (m_flags & forceLeaveWhenFinished)
		this->flags |= eASNF_Update;

//  	m_oneShot = false;
//  	node->getAttr("oneShot", m_oneShot);

//  	m_noAim = false;
//  	node->getAttr("noAim", m_noAim);

	m_animationLength = 0.0f;

	temp = false;
	node->getAttr("fullRootPriority", temp);
	if (temp)
	{
		m_flags |= fullRootPriority;
	}

	m_bUseLastToken = false;

	return ok;
}

Vec2 CAGAnimation::GetMinMaxSpeed( IEntity * pEntity )
{
	if (ICharacterInstance * pInst = pEntity->GetCharacter(0))
	{
		int animationId = -1;
		if (!(m_flags&hasVariationInputsInName))
			animationId = pInst->GetIAnimationSet()->GetAnimIDByCRC(m_CRC32AnimName);

		if (animationId >= 0)
		{
			return pInst->GetIAnimationSet()->GetMinMaxSpeedAsset_msec(animationId);
		}
	}
	return Vec2(0,0);
}

void CAGAnimation::Release()
{
	delete this;
}

IAnimationStateNode * CAGAnimation::Create()
{
	return this;
}

const char * CAGAnimation::GetCategory()
{
	return LAYER_NAMES[m_layer];
}

const char * CAGAnimation::GetName()
{
	return LAYER_NAMES[m_layer];
}

void CAGAnimation::SerializeAsFile(bool reading, AG_FILE *file)
{
	SerializeAsFile_NodeBase(reading, file);

	FileSerializationHelper h(reading, file);

	if (file->version >= 62)
	{
		h.Value(&m_layer);
		h.Value(&m_flags);
	}
	else
	{
		assert(reading);

		bool _ensureInStack, _forceLeaveWhenFinished, _stopCurrentAnimation, _interruptCurrentAnim, _fullRootPriority;
		int layer;
		h.Value(&layer);
		m_layer = layer;
		h.Value(&_ensureInStack);
		h.Value(&_forceLeaveWhenFinished);
		h.Value(&_stopCurrentAnimation);
		h.Value(&_interruptCurrentAnim);
		h.Value(&_fullRootPriority);
		m_flags = 0;
		if (_ensureInStack)
			m_flags |= ensureInStack;
		if (_forceLeaveWhenFinished)
			m_flags |= forceLeaveWhenFinished;
		if (_stopCurrentAnimation)
			m_flags |= stopCurrentAnimation;
		if (_interruptCurrentAnim)
			m_flags |= interruptCurrentAnim;
		if (_fullRootPriority)
			m_flags |= fullRootPriority;
	}
	h.Value(&m_stickyOutTime);


	string tempAnimName = m_animName.c_str();
	h.StringValue(&tempAnimName); // always save and read out the value
#ifdef USE_FILESTINGS_ON_CONSOLES
	m_animName = tempAnimName; // but don't copy it into the member variable unless needed (see below)
#endif

	// animation name related stuff
	h.Value(&m_CRC32AnimName);

	if (file->version < 62)
	{
		bool _hasVariationInputsInName;
		h.Value(&_hasVariationInputsInName);
		if (_hasVariationInputsInName)
			m_flags |= hasVariationInputsInName;
	}

	if (m_flags&hasVariationInputsInName)
	{
		m_CRC32AnimName = 0;
		m_animName = tempAnimName; 
	}

	if (file->version >= 65)
		h.Value(&m_RandomizeSpeedMulPercent);
	else
		m_RandomizeSpeedMulPercent = 0.0f;

	h.Value(&m_SPSpeedMultiplier);
	h.Value(&m_MPSpeedMultiplier);
	h.Value(&m_stayInStateUntil);
	h.Value(&m_forceInStateUntil);
	//h.Value(&m_oneShot);

	// Serialize the randomizer here too
	// determine whether or not there is one...
	bool hasRandomizer = (m_animRandomizer != NULL);
	h.Value(&hasRandomizer);
	if (hasRandomizer)
	{
		if (m_animRandomizer == NULL)
			m_animRandomizer = new CAnimationRandomizer();

		m_animRandomizer->SerializeAsFile(reading, file);
	}

}

CAGAnimation::~CAGAnimation()
{
	SAFE_DELETE(m_animRandomizer);
}

bool CAGAnimation::StartNodeAnimation(int id, SAnimationStateData& data, bool dueToRollback)
{
	if (id < 0)
		return false;

	ICharacterInstance* pICharacter = data.pEntity->GetCharacter(0);
	if (!pICharacter)
	{
		//GameWarning("Entity '%s' of class '%s' has no character attached", data.pEntity->GetName(), data.pEntity->GetClass()->GetName());
		return false;
	}

	CAnimationPlayerProxy *animPlayerProxy = data.animationProxy;
	CRY_ASSERT_MESSAGE(animPlayerProxy != NULL, "No animation player proxy defined in the data");
	if (!animPlayerProxy)
	{
		return false;
	}

	IAnimationSet* pAnimSet = pICharacter->GetIAnimationSet();

	CryCharAnimationParams params = GetAnimationParams(data);
	if (m_animRandomizer)
		m_animRandomizer->SetAnimationParams(params);

	if (params.m_nFlags & CA_LOOP_ANIMATION)
	{
		m_flags |= isLooping;
	}
	else
	{
		m_flags &= ~isLooping;
	}

	if (m_stickyOutTime > 0.0f)
	{
		//--- TSB: Only calculate the animation length if we're going to need it.
		m_animationLength = fabsf( pAnimSet->GetDuration_sec(id) / params.m_fPlaybackSpeed );
	}

	CryCharAnimationParams backParams;
	const CAnimation* anim = data.animationProxy->GetTopAnimation(data.pEntity, m_layer);
	if (anim)
	{
		backParams = anim->m_AnimParams;
		if (backParams.m_nFlags & CA_LOOP_ANIMATION)
		{
			if (params.m_nFlags & CA_START_AFTER)
			{
				//GameWarning("Trying to start an animation after a looping animation; ignoring START_AFTER flag");
				params.m_nFlags &= ~CA_START_AFTER;
			}
		}
	}

	if (dueToRollback)
	{
		if (params.m_nFlags & CA_REPEAT_LAST_KEY)
			return true;

		// this animation has already started fading out
		// we need to add it to the queue again (modified flags should help)
		params.m_nFlags |= CA_ALLOW_ANIM_RESTART;
		params.m_nFlags |= CA_TRANSITION_TIMEWARPING;
		params.m_nFlags &= ~CA_START_AFTER;
		params.m_nFlags &= ~CA_START_AT_KEYTIME;
		params.m_fTransTime = 0.01f;
	}

	if (data.hurried)
	{
		data.hurried = false;
		params.m_nFlags &= ~(CA_START_AT_KEYTIME | CA_START_AFTER);
		params.m_fTransTime = 0.2f;
	}

	if (m_flags&stopCurrentAnimation)
		animPlayerProxy->StopAnimationInLayer(data.pEntity, params.m_nLayerID, 0.0f );


	bool result = false;

	if (m_animRandomizer && m_animRandomizer->GetAnimationSwitchingEnabled() && params.m_nFlags & CA_LOOP_ANIMATION)
		this->flags |= eASNF_Update;

	// there could be one (or more) animation already in the queue with that same token
	// so change it (otherwise GetAnimation will retrieve the wrong animation)
	
	if(!m_bUseLastToken)
	{
		CAnimation* prevAnim = NULL;
		do 
		{
			prevAnim = const_cast<CAnimation*>(data.animationProxy->GetAnimation(data.pEntity, m_layer, params.m_nUserToken));
			if (prevAnim)
				prevAnim->m_AnimParams.m_nUserToken += 1;
		} while (prevAnim != NULL);
	}
	
	m_lastUserToken = params.m_nUserToken;

	result = animPlayerProxy->StartAnimationById(
		data.pEntity, 
		id, 
		params );

	if ( result )
	{
		const CAnimation* animChk = data.animationProxy->GetAnimation(data.pEntity, m_layer, params.m_nUserToken);
		CRY_ASSERT( animChk != NULL );
	}
	else
	{
		backParams.m_nUserToken = params.m_nUserToken;
	}

	return result;
}

bool CAGAnimation::IsAnimTopOfStack(int id, const SAnimationStateData& data) const
{
	ICharacterInstance* pCharacter = data.pEntity->GetCharacter(0);
	ISkeletonAnim* pSkeletonAnim = pCharacter ? pCharacter->GetISkeletonAnim() : NULL;

	bool bIsAnimTopOfStack = false;

	if (pSkeletonAnim)
	{
		const int iNumAnims = pSkeletonAnim->GetNumAnimsInFIFO(m_layer);
		if (iNumAnims > 0)
		{
			const CAnimation& anim = pSkeletonAnim->GetAnimFromFIFO(m_layer, iNumAnims - 1);

			int32 topAnimId = anim.m_nAnimID;
			bIsAnimTopOfStack = (anim.m_AnimParams.m_nUserToken == m_lastUserToken) && (id == topAnimId);
		}
	}

	return bIsAnimTopOfStack;
}

int CAGAnimation::RetrieveAnimationId( const CAnimationGraphState* pState, IAnimationSet* pAnimSet )
{	
	// TODO: Consider ways to optimize the animation lookup, baring in mind the same AG can be shared by multiple
	// (possibly different) animation sets.

	// Lookup directly by CRC is no randomness/variations
	if (!(m_flags&hasVariationInputsInName))
		return pAnimSet->GetAnimIDByCRC(m_CRC32AnimName);

	// Otherwise resolve the name to use
	CCryName animation0 = m_animName;

	// If a randomizer is registered, use its latest animation
	if (m_animRandomizer)
		animation0 = m_animRandomizer->GetCurrentAnimationName();
	
	stack_string expandedName;
	if (pState)
	{
		// Expand any variable inputs based on current state
		expandedName = pState->ExpandVariationInputs(animation0.c_str());
	}
	else
	{
		expandedName = animation0.c_str();
	}

	return pAnimSet->GetAnimIDByName(expandedName.c_str());
}

bool CAGAnimation::HasAnimation()
{
	return m_CRC32AnimName != 0 || m_animRandomizer || (m_flags&hasVariationInputsInName);
}

#undef LAYER_NAMES
